/** ****************************************************************************
@defgroup KeyMatrix KeyMatrix
@ingroup BSP_External
@brief KeyMatrix 驱动
@addtogroup KeyMatrix
@{
*******************************************************************************/

#include <string.h>
#include "key_matrix.h"

#define LOGTAG "KEY_MATRIX"


void key_matrix_init(driver_key_matrix_t *dev, uint32_t rowPins, uint32_t colPins, matrix_key_state_t** keyStates) {
	if(!dev || !rowPins || !colPins || !keyStates) { return; }
	dev->pollTaskId = NULL;
	dev->listen = false;
	dev->rowPins = rowPins;
	dev->colPins = colPins;
	dev->keyStates = keyStates;

	// 计算有效的行列数量
	dev->rowCount = 0; dev->colCount = 0;
	for(int i=0; i<32; i++) {
		if(rowPins & BV(i)) { dev->rowCount++; }
		if(colPins & BV(i)) { dev->colCount++; }
	}
}

void key_matrix_setupCallbacks(
	driver_key_matrix_t* dev,
	FUNC_onMatrixKeyEventCallback onDown,        void* cookieDown,
	FUNC_onMatrixKeyEventCallback onUp,          void* cookieUp,
	FUNC_onMatrixKeyEventCallback onClick,       void* cookieClick,
	FUNC_onMatrixKeyEventCallback onLongPress1,  void* cookieLongPress1,
	FUNC_onMatrixKeyEventCallback onLongPress2,  void* cookieLongPress2 )
{
	if(!dev) { return; }
	dev->onDown       = onDown;         dev->cookieDown       = cookieDown;
	dev->onUp         = onUp;           dev->cookieUp         = cookieUp;
	dev->onClick      = onClick;        dev->cookieClick      = cookieClick;
	dev->onLongPress1 = onLongPress1;   dev->cookieLongPress1 = cookieLongPress1;
	dev->onLongPress2 = onLongPress2;   dev->cookieLongPress2 = cookieLongPress2;
}

static matrix_key_state_t* _getKeyState(driver_key_matrix_t* dev, uint8_t row, uint8_t col) {
	uint32_t offset = sizeof(matrix_key_state_t) * (dev->colCount * row + col);
	return (matrix_key_state_t*)(((uint8_t*)dev->keyStates) + offset);
}

ButtonState key_matrix_getState(driver_key_matrix_t* dev, uint8_t row, uint8_t col) {
	return _getKeyState(dev, row, col)->keyDownTime ? BUTTON_DOWN : BUTTON_UP;
}

/// @brief 按键状态轮询处理循环
static OSTaskDef(_pollTask) {
	if(!cookieLen || !cookieData) { return; }
	driver_key_matrix_t *dev = (driver_key_matrix_t*)getPointer(cookieData);
	if(!dev || !dev->rowPins || !dev->colPins || !dev->keyStates) { return; }	
	uint32_t rowStates, colStates;

	// 为减少反复切换行列扫描模式，根据上次扫描完成时的状态，直接读行或列
	if(dev->lastScanRowMode) {
		// 获取所有的行引脚状态(低电平表示按下的行)
		rowStates = GPIOS_GetLevel(dev->rowPins);
		
		// 设置为扫列模式
		dev->lastScanRowMode = false;
		// 把所有的行引脚配置为低电平输出
		GPIOS_Config(dev->rowPins, GPIO_MODE_OUT_PP); GPIOS_SetLevel(dev->rowPins, 0);
		// 把所有的列引脚配置为上拉输入
		GPIOS_Config(dev->colPins, GPIO_MODE_IN_PU);
		// 获取所有的列引脚状态(低电平表示按下的列)
		colStates = GPIOS_GetLevel(dev->colPins);
	} else {
		// 获取所有的列引脚状态(低电平表示按下的列)
		colStates = GPIOS_GetLevel(dev->colPins);
		
		// 设置为扫行模式
		dev->lastScanRowMode = true;
		// 把所有的列引脚设置为低电平输出
		GPIOS_Config(dev->colPins, GPIO_MODE_OUT_PP); GPIOS_SetLevel(dev->colPins, 0);
		// 把所有的行引脚设置为上拉输入
		GPIOS_Config(dev->rowPins, GPIO_MODE_IN_PU);
		// 获取所有的行引脚状态(低电平表示按下的行)
		rowStates = GPIOS_GetLevel(dev->rowPins);
	}

	// 当前行号
	int row = -1; 
	// 检查所有的行引脚状态
	for(int i=0; i<32; i++) {
		// 跳过无效行引脚
		if(!(dev->rowPins & BV(i))) { continue; }
		// 记下当前行号、行引脚
		row++;

		// 当前列号
		int col = -1; 
		// 检查所有的列引脚状态
		for(int j=0; j<32; j++) {
			// 跳过无效列引脚
			if(!(dev->colPins & BV(j))) { continue; }
			// 记下当前列号
			col++; 
			// 当前的按键状态：只有行和列引脚状态都为低，才表示此键按下
			bool isDown = !((rowStates & BV(i)) | (colStates & BV(j)));
			// 之前的按键状态
			matrix_key_state_t *keyState = _getKeyState(dev, row, col);

			// 根据列引脚当前状态和之前的按下时间，判断事件
			if(!isDown && !keyState->keyDownTime) {
				// case1: 当前未按下，并且之前未按下 ==> 按键状态未改变，不用管它
			}
			else if(isDown && !keyState->keyDownTime) {
				// case2: 当前按下，并且之前未按下 ==> 按键刚刚按下
				// 记下按下时间为当前时间
				keyState->keyDownTime = osGetMs();
				keyState->onDownTriggered = false;
				keyState->onLongPress1Triggered = false;
				keyState->onLongPress2Triggered = false;
			}
			else if(isDown && keyState->keyDownTime) {
				// case3: 当前按下，并且之前已经按下 ==> 按键持续按下
				// 计算按键按下保持的时间
				uint32_t keepTime = osElapsedMs(keyState->keyDownTime);
				if(keepTime >= BUTTON_LONGPRESS_TIME2) {
					// 触发 LongPress2 事件
					if(dev->onLongPress2 && !keyState->onLongPress2Triggered) {
						keyState->onLongPress2Triggered = true;
						dev->onLongPress2(row, col, dev->cookieLongPress2);
					}
				} else if(keepTime >= BUTTON_LONGPRESS_TIME1) {
					// 触发 LongPress1 事件
					if(dev->onLongPress1 && !keyState->onLongPress1Triggered) {
						keyState->onLongPress1Triggered = true;
						dev->onLongPress1(row, col, dev->cookieLongPress1);
					}
				} else if(keepTime >= BUTTON_JITTER_TIME) {
					// 触发 Down 事件
					if(dev->onDown && !keyState->onDownTriggered) {
						keyState->onDownTriggered = true;
						dev->onDown(row, col, dev->cookieDown);
					}
				}
			}
			else if(!isDown && keyState->keyDownTime) {
				// case4: 当前未按下，并且之前已经按下 ==> 按键弹起
				// 计算按键按下保持的时间(防抖)
				uint32_t keepTime = osElapsedMs(keyState->keyDownTime);
				// 按键状态复位
				keyState->keyDownTime = 0;
				// 按键按下保持的时间如果过短，则不用处理(防抖)
				if(keepTime < BUTTON_JITTER_TIME) { continue; }
				// 触发 Click 事件
				if(keepTime < BUTTON_LONGPRESS_TIME1 && dev->onClick) { dev->onClick(row, col, dev->cookieClick); }
				// 触发 Up 事件
				if(dev->onUp) { dev->onUp(row, col, dev->cookieUp); }
			}
		}
	}
}

void key_matrix_listen(driver_key_matrix_t *dev, bool enable) {
	if(!dev || !dev->rowPins || !dev->colPins || !dev->keyStates) { return; }

	if(!dev->listen && enable) {
		// 进入监听状态前初始化状态
		memset(dev->keyStates, 0, sizeof(matrix_key_state_t) * dev->rowCount * dev->colCount);
	
		// GPIO 初始化为扫行模式
		dev->lastScanRowMode = true;
		// 把所有的列引脚设置为低电平输出
		GPIOS_Config(dev->colPins, GPIO_MODE_OUT_PP); GPIOS_SetLevel(dev->colPins, 0);
		// 把所有的行引脚设置为上拉输入
		GPIOS_Config(dev->rowPins, GPIO_MODE_IN_PU);
	}

	dev->listen = enable;

	if(!dev->pollTaskId && enable) {
		// 创建轮询任务
		dev->pollTaskId = osTaskRunRoutine(_pollTask, sizeof(void*), &dev);
	} else if(dev->pollTaskId && !enable) {
		// 如果之前已经创建了轮询任务，但要停止监听，就必须停止任务
		osTaskDestroy(&dev->pollTaskId);
	}
}


/// @}
